/*
 *  Copyright 2019 Sergey Khabarov, sergeykhbr@gmail.com
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

#include "api_core.h"
#include "d2l_d.h"

namespace debugger {

Double2Long::Double2Long(sc_module_name name_, bool async_reset) :
    sc_module(name_),
    i_clk("i_clk"),
    i_nrst("i_nrst"),
    i_ena("i_ena"),
    i_signed("i_signed"),
    i_w32("i_w32"),
    i_a("i_a"),
    o_res("o_res"),
    o_overflow("o_overflow"),
    o_underflow("o_underflow"),
    o_valid("o_valid"),
    o_busy("o_busy") {
    async_reset_ = async_reset;

    SC_METHOD(comb);
    sensitive << i_nrst;
    sensitive << i_ena;
    sensitive << i_signed;
    sensitive << i_w32;
    sensitive << i_a;
    sensitive << r.busy;
    sensitive << r.ena;
    sensitive << r.signA;
    sensitive << r.expA;
    sensitive << r.mantA;
    sensitive << r.result;
    sensitive << r.op_signed;
    sensitive << r.w32;
    sensitive << r.mantPostScale;
    sensitive << r.overflow;
    sensitive << r.underflow;

    SC_METHOD(registers);
    sensitive << i_nrst;
    sensitive << i_clk.pos();
};

void Double2Long::generateVCD(sc_trace_file *i_vcd, sc_trace_file *o_vcd) {
    if (o_vcd) {
        sc_trace(o_vcd, i_ena, i_ena.name());
        sc_trace(o_vcd, i_signed, i_signed.name());
        sc_trace(o_vcd, i_w32, i_w32.name());
        sc_trace(o_vcd, i_a, i_a.name());
        sc_trace(o_vcd, o_res, o_res.name());
        sc_trace(o_vcd, o_valid, o_valid.name());
        sc_trace(o_vcd, o_busy, o_busy.name());

        std::string pn(name());
        sc_trace(o_vcd, r.ena, pn + ".r_ena");
        sc_trace(o_vcd, r.result, pn + ".r_result");
        sc_trace(o_vcd, r.op_signed, pn + ".r_op_signed");
        sc_trace(o_vcd, r.mantPostScale, pn + ".r_mantPostScale");
        sc_trace(o_vcd, expDif, pn + ".expDif");
        sc_trace(o_vcd, mantPreScale, pn + ".mantPreScale");
        sc_trace(o_vcd, mantPostScale, pn + ".mantPostScale");
    }
}

void Double2Long::comb() {
    sc_uint<53> mantA;
    bool expDif_gr;     // greater than 1023 + 63
    bool expDif_lt;     // less than 1023
    bool overflow;
    bool underflow;
    sc_uint<11> expMax;
    sc_uint<6> expShift;
    bool resSign;
    sc_uint<64> resMant;
    sc_uint<64> res;

    v = r;

    v.ena = (r.ena.read()(1, 0), (i_ena.read() & !r.busy));

    mantA(51, 0) = i_a.read()(51, 0);
    mantA[52] = 0;
    if (i_a.read()(62, 52) != 0) {
        mantA[52] = 1;
    }

    if (i_ena.read()) {
        v.busy = 1;
        v.signA = i_a.read()[63];
        v.expA = i_a.read()(62, 52);
        v.mantA = mantA;
        v.op_signed = i_signed.read();
        v.w32 = i_w32.read();
        v.overflow = 0;
        v.underflow = 0;

        // Just for run-rime control (not for VHDL)
        v.a_dbg = i_a;
        v.reference_res = compute_reference(i_signed.read(), i_a.read());
    }

    // (1086 - expA)[5:0]
    expShift = 0x3e - r.expA.read()(5, 0);
    if (r.w32.read() == 1) {
        if (r.op_signed.read()) {
            expMax = 1053;
        } else {
            expMax = 1085;
        }
    } else {
        if (r.op_signed.read() || r.signA.read()) {
            expMax = 1085;
        } else {
            expMax = 1086;
        }
    }
    expDif = (0, expMax) - (0, r.expA.read());

    expDif_gr = expDif[11];
    expDif_lt = 0;
    if (r.expA.read() != 0x3FF && r.expA.read()[10] == 0) {
        expDif_lt = 1;
    }

    mantPreScale = r.mantA.read().to_uint64() << 11;

    mantPostScale = 0;
    if (expDif_gr) {
        overflow = 1;
        underflow = 0;
    } else if (expDif_lt) {
        overflow = 0;
        underflow = 1;
    } else {
        overflow = 0;
        underflow = 0;
        // Multiplexer, probably switch case in rtl
        for (unsigned i = 0; i < 64; i++) {
            if (expShift == i) {
                mantPostScale = mantPreScale >> i;
            }
        }
    }

    if (r.ena.read()[0] == 1) {
        v.overflow = overflow;
        v.underflow = underflow;
        v.mantPostScale = mantPostScale;
    }

    // Result multiplexers:
    resSign = (r.signA.read() || r.overflow.read()) && !r.underflow.read();
    if (r.signA.read() == 1) {
        resMant = ~r.mantPostScale.read() + 1;
    } else {
        resMant = r.mantPostScale.read();
    }

    res = resMant;
    if (r.op_signed.read() == 1) {
        if (resSign == 1) {
            if (r.w32.read() == 1) {
                res(63, 31) = ~0ull;
            } else {
                res[63] = 1;
            }
        }
    } else {
        if (r.w32.read() == 1) {
            res(63, 32) = 0;
        } else if (r.overflow.read()) {
            res[63] = 1;
        }
    }

    if (r.ena.read()[1] == 1) {
        v.result = res;
        v.busy = 0;
    }

    if (!async_reset_ && i_nrst.read() == 0) {
        R_RESET(v);
    }

    o_res = r.result;
    o_overflow = r.overflow;
    o_underflow = r.underflow;
    o_valid = r.ena.read()[2];
    o_busy = r.busy;
}

void Double2Long::registers() {
    if (async_reset_ && i_nrst.read() == 0) {
        R_RESET(r);
    } else {
        r = v;
    }
}

uint64_t Double2Long::compute_reference(bool op_signed, uint64_t a) {
    Reg64Type ra, ret;
    ra.val = a;
    if (op_signed) {
        ret.ival = static_cast<int64_t>(ra.f64);
    } else {
        ret.val = static_cast<uint64_t>(ra.f64);
    }
    return ret.val;
}

}  // namespace debugger

